package cn.enited.devtools.apiaggregator.api.utils;

import cn.enited.devtools.apiaggregator.api.vo.ApiParameter;
import cn.enited.devtools.apiaggregator.api.vo.RemoteService;
import cn.enited.devtools.apiaggregator.consts.ApiConst;
import cn.enited.devtools.apiaggregator.consts.CommonConst;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class SrvVariableUtil {

    public static final String VAR_TOKEN_HEADER = "#";
    public static final String VAR_TOKEN_SERVICE = "@";
    public static final String VAR_TOKEN_ARRAY = "$";
    public static final String VAR_TOKEN_ARRAY_ESCAPED = "\\" + VAR_TOKEN_ARRAY;
    public static final String VAR_TOKEN_ARRAY_OBJECT = VAR_TOKEN_ARRAY + "_";
    // 提取变量名的正则，格式:{变量名}
    private static final Pattern REGEX_PARAM_NAME = Pattern.compile("\\{([^}]*)\\}");
    // 匹配数组参数名的正则，格式：Java变量名$数字
    private static final Pattern REGEX_ARRAY_INDEX_VAR = Pattern.compile("^[a-zA-Z_$][a-zA-Z0-9_$]*(\\$\\d+)$");

    public static boolean isVariable(String str) {
        return REGEX_PARAM_NAME.matcher(str).matches();
    }

    public static boolean isHeaderVar(String variable) {
        return StrUtil.startWith(variable, VAR_TOKEN_HEADER);
    }

    public static boolean isServiceNameVar(String variable) {
        return StrUtil.startWith(variable, VAR_TOKEN_SERVICE);
    }

    public static boolean isArrayVar(String variable) {
        return StrUtil.endWith(variable, VAR_TOKEN_ARRAY_OBJECT) || REGEX_ARRAY_INDEX_VAR.matcher(variable).matches();
    }

    public static Optional<Integer> extractIndexFromArrayVar(String variable) {
        String index = StrUtil.subAfter(variable, VAR_TOKEN_ARRAY, true);
        if (!NumberUtil.isNumber(index)) {
            return Optional.empty();
        }
        return Optional.of(NumberUtil.parseInt(index));
    }

    public static List<String> extractParamNames(String str) {
        List<String> paramNames = new ArrayList<>();
        Matcher m = REGEX_PARAM_NAME.matcher(str);
        while (m.find()) {
            String paramName = m.group();
            if (StrUtil.isNotBlank(paramName) && !StrUtil.equals(StrUtil.cleanBlank(paramName), "{}")) {
                paramNames.add(StrUtil.removeAny(paramName, "{", "}"));
            }
        }
        return paramNames;
    }

    public static Map<String, Object> extractParamValue(RemoteService service, HttpServletRequest apiRequest,
                                                        JSONObject apiRequestBody, Map<String, Object> serviceResponse) {
        int capacity = MapUtil.isNotEmpty(service.getPathVariableParameters()) ? service.getPathVariableParameters().size() : 0;
        capacity += MapUtil.isNotEmpty(service.getHeaderParameters()) ? service.getHeaderParameters().size() : 0;
        capacity += MapUtil.isNotEmpty(service.getRequestBodyParameters()) ? service.getRequestBodyParameters().size() : 0;
        Map<String, Object> varValues = new HashMap<>(capacity);
        // PathVariable
        if (MapUtil.isNotEmpty(service.getPathVariableParameters())) {
            service.getPathVariableParameters().forEach((key, param) -> {
                putVarValueIfAbsent(varValues, key, param, apiRequest, apiRequestBody, serviceResponse);
            });
        }
        // Header
        if (MapUtil.isNotEmpty(service.getHeaderParameters())) {
            service.getHeaderParameters().forEach((key, var, param) -> {
                putVarValueIfAbsent(varValues, var, ApiParameter.empty(), apiRequest, apiRequestBody, serviceResponse);
            });
        }
        // Request Body
        if (MapUtil.isNotEmpty(service.getRequestBodyParameters())) {
            service.getRequestBodyParameters().forEach((key, param) -> {
                putVarValueIfAbsent(varValues, key, param, apiRequest, apiRequestBody, serviceResponse);
            });
        }

        // remove null value before return
        return MapUtil.removeNullValue(varValues);
    }

    /**
     * 提取所有的变量结果缓存到map中
     * @param varValues 变量map，不包含值为null的key，key: 变量全名(一定要与service中定义的保持一致，并且是全名，也就是{}里的变量名), value: 变量值
     * @param key 变量全名(一定要与service中定义的保持一致，并且是全名，也就是{}里的变量名)
     * @param param service分析后分解的参数对象
     * @param apiRequest 客户端请求
     * @param apiRequestBody 客户端请求body
     * @param serviceResponse service返回结果的map
     */
    private static void putVarValueIfAbsent(Map<String, Object> varValues, String key, ApiParameter param,
                                            HttpServletRequest apiRequest, JSONObject apiRequestBody,
                                            Map<String, Object> serviceResponse) {
        if (!varValues.containsKey(key)) {
            Object value = isHeaderVar(key)
                    ? getApiRequestHeaderValue(key, apiRequest)
                    : (isServiceNameVar(key)
                        ? getRemoteServiceResponseValue(param, serviceResponse)
                        : getApiRequestBodyValue(param, apiRequestBody));
            varValues.put(key, value);
        }
    }

    private static String getApiRequestHeaderValue(String key, HttpServletRequest apiRequest) {
        String name = StrUtil.subSuf(key, CommonConst.STR_START_INDEX);
        return apiRequest.getHeader(name);
    }

    private static Object getRemoteServiceResponseValue(ApiParameter apiParameter, Map<String, Object> serviceResponse) {
        if (apiParameter.isNotEmpty() && serviceResponse.containsKey(apiParameter.getName())) {
            return findResult(apiParameter, serviceResponse.get(apiParameter.getName()));
        }
        return null;
    }

    /**
     * 从object中提取参数结果
     * @param apiParameter service分析后分解的参数对象
     * @param object 可能是原始类型、JSON数组或JSON对象，当是JSON对象时会递归取值
     * @return
     */
    private static Object findResult(ApiParameter apiParameter, Object object) {
        if (Objects.isNull(object)) {
            return null;
        }
        if (apiParameter.getType() == ApiConst.PARAM_TYPE.JSON_ARRAY && object instanceof JSONArray) {
            JSONArray jsonArray = (JSONArray) object;
            if (Objects.nonNull(apiParameter.getArrayIndex())) {
                Object arrayItem = jsonArray.get(apiParameter.getArrayIndex());
                if (Objects.nonNull(apiParameter.getField())) {
                    return findResult(apiParameter.getField(), arrayItem);
                }
                // 子对象为空说明需要返回数组的某个索引项作为结果，索引项可以是原始类型也可以是JSON对象类型
                return arrayItem;
            }
            // 没有指定数组索引说明需要返回整个数组作为结果
            return jsonArray;
        } else if (apiParameter.getType() == ApiConst.PARAM_TYPE.RT_OBJECT && object instanceof JSONObject) {
            JSONObject jsonObject = (JSONObject) object;
            // JSON对象要从下一级开始查找，例如：@getOrders.data#0.orderId，如果当前变量是getOrders则应该查找是否存在data字段
            if (Objects.nonNull(apiParameter.getField())) {
                ApiParameter field = apiParameter.getField();
                if (jsonObject.containsKey(field.getName())) {
                    Object fieldValue = jsonObject.get(field.getName());
                    return findResult(field, fieldValue);
                } else {
                    // 结果对象中没有子对象/字段的值，返回null结束递归
                    return null;
                }
            }
            // 如果没有子对象/字段说明到头了，返回当前的对象作为结果
            return jsonObject;
        }
        return object;
    }

    private static Object getApiRequestBodyValue(ApiParameter apiParameter, JSONObject apiRequestBody) {
        if (apiParameter.isNotEmpty() && Objects.nonNull(apiRequestBody) && !apiRequestBody.isEmpty()) {
            return findResult(apiParameter, apiRequestBody);
        }
        return null;
    }

    public static Object getParamValue(String key, Map<String, Object> varValues) {
        return varValues.get(key);
    }

    public static String getStringParamValue(String key, Map<String, Object> varValues) {
        if (varValues.containsKey(key)) {
            Object object = varValues.get(key);
            if (object instanceof JSON) {
                return JSON.toJSONString(object);
            } else {
                return String.valueOf(object);
            }
        }
        return StrUtil.EMPTY;
    }

    public static String formatServicePath(RemoteService service, Map<String, Object> varValues) {
        if (MapUtil.isEmpty(service.getPathVariableParameters())) {
            return service.getPath();
        }
        String path = service.getPath();
        for (String var : service.getPathVariableParameters().keySet()) {
            String searchStr = String.format("{%s}", var);
            String replacement = getStringParamValue(var, varValues);
            path = StrUtil.replace(path, searchStr, replacement);
        }
        return path;
    }
}
